using System;
using System.Collections.Generic;
using System.Security;
using System.Linq;
using BusinessLogic.Connectivity.Entities;
using BusinessLogic.Connectivity.Interfaces;
using BusinessLogic.Managers;
using BusinessLogic.Managers.Security;
using BusinessLogic.Mappers;
using Common.Core;
using DataAccess.Services;
using SecurityManager=BusinessLogic.Managers.Security.SecurityManager;
using BusinessLogic.BusinessEntity.Entities;

namespace Server.Container
{
	internal class Server : IServerContract
	{

		public ServerResponse<DocumentEnvelope> ReceiveDocument(DocumentEnvelope envelope, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() => AddDocumentForProcessing(ticket, envelope), envelope, ticket);
		}

		public ServerResponse<User> GetSessionTicket(Credentials credentials)
		{
			return new ServerExceptionCatcher().Execute(() => CreateSessionTicket(credentials), null, null);
		}


		public ServerResponse<IList<DocumentEnvelope>> GetSentEnvelopes(string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket sessionTicket = CheckTicketAndRoles<DocumentReceiver>(ticket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						var envelopes =
							MapperFactory.GetDocumentEnvelopeMapper(serviceManager).
								SelectBySenderId(sessionTicket.UserId);

						return new ServerResponse<IList<DocumentEnvelope>>(
							ServerResponseCode.Success, string.Empty, envelopes.ToList(), ticket);
					}
				}, null, ticket);
		}

		private static ServerResponse<DocumentEnvelope> AddDocumentForProcessing(string ticket, DocumentEnvelope envelope)
		{
			SessionTicket sessionTicket = CheckTicketExistance(ticket);

			CheckRoles(sessionTicket, typeof(DocumentReceiver));

			envelope.SenderId = sessionTicket.UserId;
			DocumentReceiver.ReceiveDocument(envelope);

			return new ServerResponse<DocumentEnvelope>(ServerResponseCode.Success, "Document has been sucessfully received.", envelope, ticket);
		}

		private static SessionTicket CheckTicketExistance(string ticket)
		{
			var sessionTicket = ApplicationCache.SessionTickets[ticket] as SessionTicket;

			if (sessionTicket == null)
			{
				throw new SecurityException(string.Format("Ticket '{0}' doesn't exist or expired! Try to relogin or turn to system administrator", ticket));
			}

			return sessionTicket;
		}

		private static void CheckRoles(SessionTicket ticket, Type protectedType)
		{
			if (!SecurityManager.CheckWhetherAccessIsGranted(protectedType, ticket.Roles))
			{
				throw new SecurityException(
					string.Format("The user assigned to the ticket '{0}' has not enough roles to perform the operation.", ticket.SessionId));
			}
		}

		private static ServerResponse<User> CreateSessionTicket(Credentials credentials)
		{
			using (IServiceManager serviceManager = new ServiceManager())
			{
				int? userID = MapperFactory.GetUserMapper(serviceManager).EnsureUserCredentials(credentials.Login,
				                                                                                      credentials.Password);

				if (!userID.HasValue)
				{
					throw new SecurityException("There's no client with credentials provided.");
				}

				IEnumerable<Role> roles = MapperFactory.GetUserMapper(serviceManager).GetUserRoles(userID.Value);
				string sessionId = Guid.NewGuid().ToString();

				var ticket = new SessionTicket(sessionId, roles, userID.Value);

				ApplicationCache.SessionTickets[sessionId] = ticket;

				var user = MapperFactory.GetUserMapper(serviceManager).GetById(userID.Value);

				return new ServerResponse<User>(ServerResponseCode.Success, "Ticket has been created on server.", user, sessionId);
			}
		}

		private static SessionTicket CheckTicketAndRoles<T>(string ticket)
		{
			SessionTicket sessionTicket = CheckTicketExistance(ticket);

			CheckRoles(sessionTicket, typeof(T));

			return sessionTicket;
		}


		#region Account operations 

		public ServerResponse<Account> CreateAccount(Account account, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var accountMapper = MapperFactory.GetAccountMapper(serviceManager);

					var acocunts = accountMapper.GetAllAccounts();
					var accountsNumbers = acocunts.Select(_ => _.Number);

					if (!accountsNumbers.Contains(account.Number))
					{
						accountMapper.Insert(account);
						return new ServerResponse<Account>(ServerResponseCode.Success, string.Empty, null, ticket);
					}
					else
					{
						return new ServerResponse<Account>(ServerResponseCode.Failure, "The user already has the account with the provided number!", null, ticket);
					}
				}
			}, null, string.Empty);
		}

		public ServerResponse<Account> DeleteAccountById(int accountID, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					MapperFactory.GetAccountMapper(serviceManager).Delete(accountID);

					return new ServerResponse<Account>(ServerResponseCode.Success, "Account successfully deleted!", null, ticket);
				}
			}, null, string.Empty);
		}

		public ServerResponse<Account> ReplenishAccount(int accountID, decimal sumToAdd, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var accountMapper = MapperFactory.GetAccountMapper(serviceManager);
					var account = accountMapper.GetAccountByID(accountID);
					accountMapper.UpdateBalance(accountID, account.Balance + sumToAdd);

					return new ServerResponse<Account>(ServerResponseCode.Success, string.Empty, null, ticket);
				}
			}, null, string.Empty);
		}

		public ServerResponse<IList<RemittanceAdvice>> GetReceivedRemittanceAdvices(DateTime startDate, DateTime endDate, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			                                            	{
			                                            		SessionTicket sessionTicket = CheckTicketAndRoles<DocumentReceiver>(ticket);

			                                            		using (IServiceManager serviceManager = new ServiceManager())
			                                            		{
			                                            			IEnumerable<RemittanceAdvice> advices = MapperFactory.GetRemittanceAdviceMapper(serviceManager).GetReceivedAdvices(sessionTicket.UserId, startDate, endDate);

			                                            			return new ServerResponse<IList<RemittanceAdvice>>(ServerResponseCode.Success, string.Empty, advices.ToList(), ticket);
			                                            		}
			                                            	}, null, ticket);
		}

		public ServerResponse<IList<Account>> GetClientAccounts(string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<DocumentReceiver>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var accounts =
						MapperFactory.GetAccountMapper(serviceManager).GetClientAccounts(
							sessionTicket.UserId);

					return new ServerResponse<IList<Account>>(
						ServerResponseCode.Success, string.Empty, accounts.ToList(), ticket);
				}
			}, null, ticket);
		}


		public ServerResponse<IList<Account>> GetAllAccounts(string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var accounts = MapperFactory.GetAccountMapper(serviceManager).GetAllAccounts();

					return new ServerResponse<IList<Account>>(
						ServerResponseCode.Success, string.Empty, accounts, ticket);
				}
			}, null, ticket);
		}

		public ServerResponse<IList<Account>> GetAllAccountsForUser(string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<DocumentReceiver>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var accounts = MapperFactory.GetAccountMapper(serviceManager).GetAllAccounts();

					return new ServerResponse<IList<Account>>(
						ServerResponseCode.Success, string.Empty, accounts, ticket);
				}
			}, null, ticket);
		}

		#endregion


		#region User operations

		public ServerResponse<User> CreateUser(User user, string sessionTicket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket ticket = CheckTicketAndRoles<BankApplication>(sessionTicket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						var userMapper = MapperFactory.GetUserMapper(serviceManager);

						if (!userMapper.Exists(user.Login))
						{
							var userID = userMapper.Insert(user);
							user.ID = userID;

							return new ServerResponse<User>(ServerResponseCode.Success, "The user successfully created!", user, sessionTicket);
						}
						else
						{
							return new ServerResponse<User>(ServerResponseCode.Failure, "The user with such login already exists!", user, sessionTicket);
						}
					}
				}, null, string.Empty);
		}



		public ServerResponse<IList<User>> GetAllUsers(string sessionTicket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket ticket = CheckTicketAndRoles<BankApplication>(sessionTicket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						IEnumerable<User> clients = MapperFactory.GetUserMapper(serviceManager).GetAllUsers();
						return new ServerResponse<IList<User>>(ServerResponseCode.Success, string.Empty, clients.ToList(), sessionTicket);
					}

				}, null, string.Empty);
		}


		public ServerResponse<User> DeleteUser(int clientId, string sessionTicket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket ticket = CheckTicketAndRoles<BankApplication>(sessionTicket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						MapperFactory.GetUserMapper(serviceManager).Delete(clientId);

						return new ServerResponse<User>(ServerResponseCode.Success, "User deleted successfully.", null, sessionTicket);
					}

				}, null, string.Empty);
		}


		public ServerResponse<User> SetUserPublicKey(int userID, string publicKey, string sessionTicket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket ticket = CheckTicketAndRoles<BankApplication>(sessionTicket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var userMapper = MapperFactory.GetUserMapper(serviceManager);

					var user = userMapper.GetById(userID);
					user.PublicKey = publicKey;
					userMapper.Update(user);

					return new ServerResponse<User>(ServerResponseCode.Success, "New public key assigned successfully.", null, sessionTicket);
				}

			}, null, string.Empty);
		}

		public ServerResponse<User> UpdateUser(User user, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						var userMapper = MapperFactory.GetUserMapper(serviceManager);

						var originalUser = userMapper.GetById(user.ID);

						if (!userMapper.Exists(user.Login) || user.Login == originalUser.Login)
						{
							userMapper.Update(user);

							return new ServerResponse<User>(ServerResponseCode.Success, string.Empty, null, ticket);
						}
						else
						{
							return new ServerResponse<User>(ServerResponseCode.Failure, "The user with such login already exists!", user, ticket);
						}
						
					}
				}, null, string.Empty);
		}


		public ServerResponse<User> AssignRole(int userId, Role role, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var userMapper = MapperFactory.GetUserMapper(serviceManager);

					var userRoles = userMapper.GetUserRoles(userId);
					if (!userRoles.Contains(role))
					{
						userMapper.AssignRole(userId, role);
						return new ServerResponse<User>(ServerResponseCode.Success, "The role has been successfully assigned.", null, string.Empty);
					}
					else
					{
						return new ServerResponse<User>(ServerResponseCode.Failure, "The user already has this role!", null, string.Empty);
					}
				}

			}, null, string.Empty);
		}


		public ServerResponse<User> DeleteRole(int userId, Role role, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					MapperFactory.GetUserMapper(serviceManager).DeleteRole(userId, role);

					return new ServerResponse<User>(ServerResponseCode.Success, "The role has been successfully deleted.", null, string.Empty);
				}

			}, null, string.Empty);
		}




		public ServerResponse<IDictionary<User, IList<Role>>> GetUsersRolePairs(string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var usersRolesPairs = MapperFactory.GetUserMapper(serviceManager).GetUserRolePairs();

					return new ServerResponse<IDictionary<User, IList<Role>>>(ServerResponseCode.Success, null, usersRolesPairs, string.Empty);
				}

			}, null, string.Empty);
		}


		public ServerResponse<IList<Role>> GetRoles()
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					using (IServiceManager serviceManager = new ServiceManager())
					{
						var roles = MapperFactory.GetUserMapper(serviceManager).GetRoles();

						return new ServerResponse<IList<Role>>(ServerResponseCode.Success, null, roles, string.Empty);
					}

				}, null, string.Empty);
		}


		public ServerResponse<IList<Role>> GetUserRoles(int userID, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

					using (IServiceManager serviceManager = new ServiceManager())
					{
						var roles = MapperFactory.GetUserMapper(serviceManager).GetUserRoles(userID);

						return new ServerResponse<IList<Role>>(ServerResponseCode.Success, null, roles, string.Empty);
					}

				}, null, string.Empty);
		}

		#endregion


		#region Currency operations


		public ServerResponse<CurrencyConversion> UpdateCurrencyConversion(CurrencyConversion conversion, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					MapperFactory.GetCurrencyConversionMapper(serviceManager).Update(conversion);

					return new ServerResponse<CurrencyConversion>(
						ServerResponseCode.Success, string.Empty, conversion, string.Empty);
				}
			}, null, string.Empty);
		}

		public ServerResponse<IList<RemittanceAdvice>> GetSentRemittanceAdvices(DateTime startDate, DateTime endDate, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			                                            	{
																SessionTicket sessionTicket = CheckTicketAndRoles<DocumentReceiver>(ticket);

			                                            		using (IServiceManager serviceManager = new ServiceManager())
			                                            		{
																	IEnumerable<RemittanceAdvice> advices = MapperFactory.GetRemittanceAdviceMapper(serviceManager).GetSentAdvices(sessionTicket.UserId, startDate, endDate);

																	return new ServerResponse<IList<RemittanceAdvice>>(ServerResponseCode.Success, string.Empty, advices.ToList(), ticket);
			                                            		}
			                                            	}, null, ticket);
		}


		public ServerResponse<IList<CurrencyConversion>> GetCurrencyConversions()
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					using (IServiceManager serviceManager = new ServiceManager())
					{
						var conversions =
							MapperFactory.GetCurrencyConversionMapper(serviceManager).GetAll();

						return new ServerResponse<IList<CurrencyConversion>>(
							ServerResponseCode.Success, string.Empty, conversions.ToList(), string.Empty);
					}
				}, null, string.Empty);
		}


		public ServerResponse<IList<Currency>> GetCurrencies()
		{
			return new ServerExceptionCatcher().Execute(() =>
				{
					using (IServiceManager serviceManager = new ServiceManager())
					{
						var currencies = MapperFactory.GetCurrencyMapper(serviceManager).GetSupportedCurrencies();
						return new ServerResponse<IList<Currency>>(ServerResponseCode.Success, string.Empty, currencies, string.Empty);
					}

				}, null, string.Empty);
		}


		public ServerResponse<Currency> CreateCurrency(Currency currencyToCreate, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				using (IServiceManager serviceManager = new ServiceManager())
				{
					SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

					var currencyMapper = MapperFactory.GetCurrencyMapper(serviceManager);

					var currencies = currencyMapper.GetSupportedCurrencies();
					var currencyCodes = from _ in currencies select _.Code;


					if (!currencyCodes.Contains(currencyToCreate.Code))
					{
						currencyMapper.Insert(currencyToCreate);

						// Now we need to create the currency conversions for the value we are going to create
						var currencyConversionMapper = MapperFactory.GetCurrencyConversionMapper(serviceManager);
						
						foreach (var currency in currencies)
						{
							CurrencyConversion directConversion = new CurrencyConversion();
							directConversion.ConversionRate = 1;
							directConversion.SourceCurrency = currency;
							directConversion.TargetCurrency = currencyToCreate;
							currencyConversionMapper.Insert(directConversion);

							CurrencyConversion inverseConversion = new CurrencyConversion();
							inverseConversion.ConversionRate = 1;
							inverseConversion.SourceCurrency = currencyToCreate;
							inverseConversion.TargetCurrency = currency;
							currencyConversionMapper.Insert(inverseConversion);
						}

						return new ServerResponse<Currency>(ServerResponseCode.Success, "Currency has been successfully created!", null, string.Empty);
					}
					else
					{
						return new ServerResponse<Currency>(ServerResponseCode.Failure, "Currency with such code already exists!", null, string.Empty);
					}
					
				}

			}, null, string.Empty);
		}


		public ServerResponse<Currency> DeleteCurrency(int currencyID, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					// Manual check whether the accounts with the currency being deleted exist??
					// Currently the user gets a crazy message with a SQL querry in it ... due to the constraint violation.

					MapperFactory.GetCurrencyMapper(serviceManager).Delete(currencyID);
					return new ServerResponse<Currency>(ServerResponseCode.Success, string.Empty, null, string.Empty);
				}

			}, null, string.Empty);
		}


		public ServerResponse<CurrencyConversion> CreateCurrencyConversion(CurrencyConversion conversion, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				using (IServiceManager serviceManager = new ServiceManager())
				{
					SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

					var currencyConversionMapper = MapperFactory.GetCurrencyConversionMapper(serviceManager);
					
					decimal? conversionRate = currencyConversionMapper.Get(conversion.SourceCurrency.Code, conversion.TargetCurrency.Code);

					if (!conversionRate.HasValue)
					{
						currencyConversionMapper.Insert(conversion);
						return new ServerResponse<CurrencyConversion>(ServerResponseCode.Success, "Currency conversion has been successfully created!", null, string.Empty);
					}
					else
					{
						return new ServerResponse<CurrencyConversion>(ServerResponseCode.Failure, "Such currency conversion already exists!", null, string.Empty);
					}
				}

			}, null, string.Empty);
		}


		public ServerResponse<CurrencyConversion> DeleteCurrencyConversion(int currencyConversionID, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					// conversion deletion rules?
					MapperFactory.GetCurrencyConversionMapper(serviceManager).Delete(currencyConversionID);
					return new ServerResponse<CurrencyConversion>(ServerResponseCode.Success, string.Empty, null, string.Empty);
				}

			}, null, string.Empty);
		}


		public ServerResponse<IList<DocumentEnvelope>> GetFilteredEnvelopes(string userName, DateTime periodStart, DateTime periodEnd, string ticket)
		{
			return new ServerExceptionCatcher().Execute(() =>
			{
				SessionTicket sessionTicket = CheckTicketAndRoles<BankApplication>(ticket);

				using (IServiceManager serviceManager = new ServiceManager())
				{
					var envelopeMapper = MapperFactory.GetDocumentEnvelopeMapper(serviceManager);

					var envelopes = envelopeMapper.GetAll();

					// filtering by date

					var filteredByDate =
						(from envelope in envelopes
						where envelope.SentDate >= periodStart && envelope.SentDate <= periodEnd
						select envelope).ToList();

					// filtering by sender id
					if (userName != String.Empty)
					{
						var userMapper = MapperFactory.GetUserMapper(serviceManager);
						var users = userMapper.GetAllUsers();

						var userIDsWithProvidedName =
							(from user in users
							 where user.Login == userName
							 select user.ID).ToList();

						// no no users with such id
						if (userIDsWithProvidedName.Count == 0)
						{
							return new ServerResponse<IList<DocumentEnvelope>>(ServerResponseCode.Success, string.Empty, new List<DocumentEnvelope>(), ticket);
						}

						var userId = userIDsWithProvidedName[0];

						var filteredByName =
							(from envelope in filteredByDate
							 where envelope.SenderId == userId
							 select envelope).ToList();

						return new ServerResponse<IList<DocumentEnvelope>>(ServerResponseCode.Success, string.Empty, filteredByName, ticket);
					}

					return new ServerResponse<IList<DocumentEnvelope>>(ServerResponseCode.Success, string.Empty, filteredByDate, ticket);
				}

			}, null, string.Empty);

		}


		#endregion
	}
}
